#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import time
import socket
import random
import struct

from threading import Timer

def __load_config(conf_file):
	if not os.path.exists(conf_file):
		print("{0} isn't exists, pls check".format(conf_file))
		sys.exit(-1)
		
	global CONFIG
	cname, ext = os.path.splitext(conf_file)
	config = __import__(cname)
	CONFIG = config.CONFIG

def conf_item(name):
	global CONFIG
	
	if name in CONFIG.keys():
		return CONFIG[name]
		
	return None
	
def __load_msg(mfile):
	if not os.path.exists(mfile):
		print("{0} isn't exists, pls check".format(mfile))
		sys.exit(-1)
	
	f   = open(mfile, "r")
	msg = f.read()
	f.close()
	
	return msg

PCAP_FORMAT = {
	"pcap_header_len"   : 24,
	"packet_header_len" : 16,
	"caplen_pos"	    : 8,			#caplen in packet header pos
}

def print_hex(data):
	s = ''
	for i in range(len(data)):
		s += str.format('{0:02x} ', data[i])
		if (i+1) %16 == 0:
			s += '\n'
	
	print(s)

def CalcCheckSum(data):
	sum = 0
	for i in range(0, len(data), 2):
		val = int.from_bytes(data[i:i+2], "big")
		sum += val
		
	sum = (sum >> 16) + (sum & 0xffff)
	sum += (sum >> 16)
		
	return (~sum & 0xffff)

class IpPacketPacker:
	def __init__(self, src, dst, proto):
		self.id  = random.randint(1, 65536/2)
		self.ttl = 127
		self.tos = 0
		self.flags = 0
		self.proto = proto
		self.checksum = 0
		self.hdr_len = 20
		self.total_len = 0
		self.src = socket.inet_aton(src)
		self.dst = socket.inet_aton(dst)
		
	def __PackIpHeader(self, trans_len):
		self.id += 1
		self.checksum = 0
		
		hdr = struct.pack("!HHHHHH4s4s", 4<<12|5<<8|self.tos, trans_len+20, self.id, self.flags, self.ttl<<8|self.proto, self.checksum,self.src, self.dst)
		self.checksum = CalcCheckSum(hdr)
		
		return struct.pack("!HHHHHH4s4s", 4<<12|5<<8|self.tos, trans_len+20, self.id, self.flags, self.ttl<<8|self.proto, self.checksum,self.src, self.dst)
		
		
	def PackIpPacket(self, payload):
		return self.__PackIpHeader(len(payload)) + payload

class UdpMsgPacker:
	def __init__(self, src, dst, sport, dport):
		self.ipp = IpPacketPacker(src, dst, 17)
		self.checksum = 0
		self.sport = sport
		self.dport = dport
		
	def __PackUdpHeader(self, plen):
		hdr = struct.pack("!HHHH", self.sport,self.dport, plen+8, self.checksum)
		self.checksum = CalcCheckSum(hdr)
		
		return struct.pack("!HHHH", self.sport,self.dport, plen+8, self.checksum)
	
	def PackMsg(self, payload):
		return self.ipp.PackIpPacket(self.__PackUdpHeader(len(payload)) + payload)

class PcapReader:
	def __init__(self):
		self.pcap_file = ""
		self.pcap_data = []
		self.pcap_len = 0				  	#file length
		self.pos = 0               			#initial pos
		self.packet_count = 0				#packet count
		
		
	def open(self, name):	
		try:
			f = open(name, "rb")
			self.pcap_data = f.read()
			f.close()
			
			self.pcap_file = name
			self.pcap_len = len(self.pcap_data)
			self.pos = PCAP_FORMAT["pcap_header_len"]   #skip pcap header len
			
		except IOError as e:
			print(e)
			
	def pcap_file_len(self):
		return self.pcap_len
	
	def pcap_count(self):
		return self.packet_count
		
	def get_next_packet(self):
		if (self.pos >= self.pcap_len):
			return None
		
		packet_header_len = PCAP_FORMAT["packet_header_len"]
		packet_header     = self.pcap_data[self.pos : self.pos + packet_header_len]
		caplen = int.from_bytes(packet_header[8:12], "little")
		
		self.pos += packet_header_len 			#skip packet header
		if(self.pos + caplen > self.pcap_len):
			return None
		
		packet = self.pcap_data[self.pos : self.pos + caplen]
		self.pos += caplen
		self.packet_count += 1
		
		return packet
			
	def close(self):
		self.pcap_file = ""
		self.pcap_data = []
		self.pos = 0
		self.pcap_len = 0
		self.packet_count = 0
		
	def print(self):
		print("file:{0}, len:{1}, pos:{2}, count:{3}".format(self.pcap_file, self.pcap_len, self.pos, self.packet_count))

def __rtp_packet_send(msgpacker, reader, s):
	try:
		rtp_packet = reader.get_next_packet()
		if rtp_packet is None:  #send over
			print("Total send rtp packet {0}".format(reader.pcap_count()))
			reader.close()
			s.close()
			return
		
		server_addr = conf_item("server_addr")
		msg = msgpacker.PackMsg(rtp_packet[42:])
		n = s.sendto(msg, server_addr)
		#print("do send {0}".format(n))
		
		if reader.pcap_count() % 101 == 1:
			reader.print()
		
		timer = Timer(0.02, __rtp_packet_send, (msgpacker, reader, s,))
		timer.start()
	except Exception as e:
		print(e)
	
def __rtp_msg_send():
	try:
		local_addr  = conf_item("local_addr")
		server_addr = conf_item("server_addr")
		msgpacker   = UdpMsgPacker(local_addr[0], server_addr[0], local_addr[1], server_addr[1])
		
		s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
		s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
		
		reader = PcapReader()
		rtp_pcap = conf_item("rtp_pcap")
		reader.open(rtp_pcap)
		
		__rtp_packet_send(msgpacker, reader, s)
		
	except Exception as e:
		print(e)

def __sip_msg_send():
	local_addr  = conf_item("local_addr")
	server_addr = conf_item("server_addr")
	sip         = __load_msg(conf_item("sip_msg"))
	times       = conf_item("times")
	
	msgpacker   = UdpMsgPacker(local_addr[0], server_addr[0], local_addr[1], server_addr[1])
	msg = msgpacker.PackMsg(bytes(sip, encoding = "ascii"))
	
	s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
	s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
	
	for l in range(times):
		n = s.sendto(msg, server_addr)
			
	s.close()
	print("finish, sip msg sent {0} times.".format(times))
	
msg_type = ("sip", "rtp")

if __name__ == "__main__":
	if len(sys.argv) != 3:
		print("Usage: {0} <config_file_name>  <sip|rtp>".format(sys.argv[0]))
		sys.exit(-1)
		
	type = sys.argv[2]
	if type not in msg_type:
		print("unsupport msg type [" + type + "] to send")
		sys.exit(-1)
	
	__load_config(sys.argv[1])
	
	if type.count("sip"):
		__sip_msg_send()
	
	
	if type.count("rtp"):
		__rtp_msg_send()
